feat(analyzers): code-fix for TUnit0049 to insert [MatrixDataSource] (#5613 C)#5620
feat(analyzers): code-fix for TUnit0049 to insert [MatrixDataSource] (#5613 C)#5620
Conversation
…ce] (#5613 C) Registers a code-fix on TUnit0049 (MatrixDataSourceAttributeRequired) that inserts [MatrixDataSource] on the diagnosed method or class, so users hitting the error can apply the fix directly rather than hand- editing.
There was a problem hiding this comment.
Code Review: MatrixDataSourceCodeFixProvider
Overall this is clean, well-scoped work that fits neatly into the existing code-fix pattern. The two-test approach (method + class) covers both diagnostic sites. A few observations:
Minor: SyntaxFactory.ParseName vs SyntaxFactory.IdentifierName
// Current
SyntaxFactory.ParseName("MatrixDataSource")
// More idiomatic for an unqualified name
SyntaxFactory.IdentifierName("MatrixDataSource")ParseName triggers a full parse pass and is intended for potentially-qualified names (e.g., "TUnit.Core.MatrixDataSource"). For a simple identifier, IdentifierName is the canonical choice and is used throughout Roslyn's own code-fix providers. Not a correctness issue, but prefer the precise API.
Minor: Consistency — const string Title pattern
All existing fixers (e.g., VirtualHookOverrideCodeFixProvider) extract the title into a const so it's shared between title: and equivalenceKey::
private const string Title = "Add [MatrixDataSource]";
// then:
CodeAction.Create(title: Title, ..., equivalenceKey: Title)The PR uses two separate string literals. If the title is ever renamed, it's easy to update one and forget the other (which breaks fix-all equivalence grouping). Extracting to a const keeps them in sync automatically.
Minor: Redundant using TUnit.Analyzers; in peer file
VirtualHookOverrideCodeFixProvider.cs explicitly imports using TUnit.Analyzers; for Rules, while this file omits it and relies on enclosing-namespace resolution (TUnit.Analyzers.CodeFixers is a child of TUnit.Analyzers). It compiles either way, but adding the explicit import would be consistent with the neighbouring fixer.
Not an issue: Missing using TUnit.Core;
The concern "what if the file doesn't have using TUnit.Core;?" doesn't apply here — the diagnostic only fires when [Matrix] is already present, and [Matrix] itself requires TUnit.Core, so the using is guaranteed to exist.
Not an issue: DocumentEditor vs document.WithSyntaxRoot
The choice of DocumentEditor (vs the raw WithSyntaxRoot approach in VirtualHookOverrideCodeFixProvider) is fine — DocumentEditor is actually the recommended pattern for additive changes and integrates better with the workspace's formatting pipeline.
Summary: The core logic is correct and the test coverage is solid. The three minor points above are all about consistency with existing conventions rather than correctness. Happy to approve once/if any of those are addressed, or as-is if the author prefers to leave them.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 4 |
TIP This summary will be updated as you push new changes. Give us feedback
…trixDataSource fix Addresses review feedback on #5620: - Replace SyntaxFactory.ParseName with SyntaxFactory.IdentifierName for the unqualified "MatrixDataSource" — ParseName invokes the full parser where IdentifierName constructs directly. - Extract the action title to a const so the code-action title and equivalenceKey stay in sync if either is renamed.
There was a problem hiding this comment.
Follow-up Review
The previous feedback has been addressed:
- ✅ used (not )
- ✅ extracted and used for both and
The one remaining minor point from last round — adding an explicit using TUnit.Analyzers; for consistency with VirtualHookOverrideCodeFixProvider — is cosmetic and compiles fine either way. Not a blocker.
One new observation: fallback is unreachable
SyntaxNode updated = target switch
{
MethodDeclarationSyntax method => method.AddAttributeLists(attributeList),
TypeDeclarationSyntax type => type.AddAttributeLists(attributeList),
_ => target, // dead code
};The _ branch makes editor.ReplaceNode(target, target) a no-op. TUnit0049 can only be diagnosed on a method or a type, so this branch is never reached. Consider replacing it with _ => throw new InvalidOperationException($"Unexpected node kind: {target.Kind()}")` — this turns a silent no-op (wrong fix silently produces unchanged code) into a loud invariant violation that surfaces during development/testing if the analyzer ever fires somewhere unexpected.
Overall
The implementation is correct, well-tested, and consistent with the existing code-fix patterns. The two-test suite covers both diagnostic sites cleanly. Ready to merge.
There was a problem hiding this comment.
Follow-up Review
The previous feedback has been addressed:
SyntaxFactory.IdentifierNameused (notParseName)private const string Titleextracted and used for bothtitle:andequivalenceKey:
The one remaining minor point from last round (explicit using TUnit.Analyzers; for consistency) is cosmetic and compiles fine. Not a blocker.
New observation: _ => target fallback is a silent no-op
The _ arm in the switch produces editor.ReplaceNode(target, target) — replacing a node with itself. Since TUnit0049 only fires on methods and types, this branch is unreachable. The concern is that a silent no-op is worse than a loud failure: if the analyzer ever fires somewhere unexpected, the fix would appear to succeed but produce no visible change.
Consider replacing it with an exception:
_ => throw new InvalidOperationException($"Unexpected node kind: {target.Kind()}")This turns a hidden invariant violation into an immediate, diagnosable error during development or testing, rather than a silently-wrong fix.
Overall
The implementation is correct and the two tests cover both diagnostic sites cleanly. Previous review feedback has been fully addressed. Ready to merge once the unreachable branch is handled (or intentionally kept as a safe fallback — either is defensible).
…e fix The ancestor filter guarantees target is MethodDeclarationSyntax or TypeDeclarationSyntax, so the default arm was unreachable and silently replaced the node with itself. Fail loudly if the invariant ever breaks.
There was a problem hiding this comment.
Follow-up Review — All Feedback Addressed
All three points from previous rounds have been resolved in the current diff:
- ✅
SyntaxFactory.IdentifierNameused (notParseName) - ✅
private const string Titleextracted and shared betweentitle:andequivalenceKey: - ✅
_ => throw new InvalidOperationException(...)replaces the silent no-op fallback
The cosmetic point (explicit using TUnit.Analyzers;) is the only thing untouched, and it's not a blocker — the code compiles and behaves correctly either way.
Overall
The implementation is clean, correct, and consistent with the existing code-fix patterns. Both diagnostic sites (method and class) are covered by tests. Ready to merge.
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.35.2 to 1.37.10. <details> <summary>Release notes</summary> _Sourced from [TUnit's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.37.10 <!-- Release notes generated using configuration in .github/release.yml at v1.37.10 --> ## What's Changed ### Other Changes * docs(test-filters): add migration callout for --filter → --treenode-filter by @johnkattenhorn in thomhurst/TUnit#5628 * fix: re-enable RPC tests and modernize harness (#5540) by @thomhurst in thomhurst/TUnit#5632 * fix(mocks): propagate [Obsolete] and null-forgiving raise dispatch (#5626) by @JohnVerheij in thomhurst/TUnit#5631 * ci: use setup-dotnet built-in NuGet cache by @thomhurst in thomhurst/TUnit#5635 * feat(playwright): propagate W3C trace context into browser contexts by @thomhurst in thomhurst/TUnit#5636 ### Dependencies * chore(deps): update tunit to 1.37.0 by @thomhurst in thomhurst/TUnit#5625 ## New Contributors * @johnkattenhorn made their first contribution in thomhurst/TUnit#5628 * @JohnVerheij made their first contribution in thomhurst/TUnit#5631 **Full Changelog**: thomhurst/TUnit@v1.37.0...v1.37.10 ## 1.37.0 <!-- Release notes generated using configuration in .github/release.yml at v1.37.0 --> ## What's Changed ### Other Changes * fix: stabilize flaky tests across analyzer, OTel, and engine suites by @thomhurst in thomhurst/TUnit#5609 * perf: engine hot-path allocation wins (#5528 B) by @thomhurst in thomhurst/TUnit#5610 * feat(analyzers): detect collection IsEqualTo reference equality (TUnitAssertions0016) by @thomhurst in thomhurst/TUnit#5615 * perf: consolidate test dedup + hook register guards (#5528 A) by @thomhurst in thomhurst/TUnit#5612 * perf: engine discovery/init path cleanup (#5528 C) by @thomhurst in thomhurst/TUnit#5611 * fix(assertions): render collection contents in IsEqualTo failure messages (#5613 B) by @thomhurst in thomhurst/TUnit#5619 * feat(analyzers): code-fix for TUnit0015 to insert CancellationToken (#5613 D) by @thomhurst in thomhurst/TUnit#5621 * fix(assertions): add Task reference forwarders on AsyncDelegateAssertion by @thomhurst in thomhurst/TUnit#5618 * test(asp-net): fix race in FactoryMethodOrderTests by @thomhurst in thomhurst/TUnit#5623 * feat(analyzers): code-fix for TUnit0049 to insert [MatrixDataSource] (#5613 C) by @thomhurst in thomhurst/TUnit#5620 * fix(pipeline): isolate AOT publish outputs to stop clobbering pack DLLs (#5622) by @thomhurst in thomhurst/TUnit#5624 ### Dependencies * chore(deps): update tunit to 1.36.0 by @thomhurst in thomhurst/TUnit#5608 * chore(deps): update modularpipelines to 3.2.8 by @thomhurst in thomhurst/TUnit#5614 **Full Changelog**: thomhurst/TUnit@v1.36.0...v1.37.0 ## 1.36.0 <!-- Release notes generated using configuration in .github/release.yml at v1.36.0 --> ## What's Changed ### Other Changes * fix: don't render test's own trace as Linked Trace in HTML report by @thomhurst in thomhurst/TUnit#5580 * fix(docs): benchmark index links 404 by @thomhurst in thomhurst/TUnit#5587 * docs: replace repeated benchmark link suffix with per-test descriptions by @thomhurst in thomhurst/TUnit#5588 * docs: clearer distributed tracing setup and troubleshooting by @thomhurst in thomhurst/TUnit#5597 * fix: auto-suppress ExecutionContext flow for hosted services (#5589) by @thomhurst in thomhurst/TUnit#5598 * feat: auto-align DistributedContextPropagator to W3C by @thomhurst in thomhurst/TUnit#5599 * feat: TUnit0064 analyzer + code fix for direct WebApplicationFactory inheritance by @thomhurst in thomhurst/TUnit#5601 * feat: auto-propagate test trace context through IHttpClientFactory by @thomhurst in thomhurst/TUnit#5603 * feat: TUnit.OpenTelemetry zero-config tracing package by @thomhurst in thomhurst/TUnit#5602 * fix: restore [Obsolete] members removed in v1.27 (#5539) by @thomhurst in thomhurst/TUnit#5605 * feat: generalize OTLP receiver for use outside TUnit.Aspire by @thomhurst in thomhurst/TUnit#5606 * feat: auto-configure OpenTelemetry in TestWebApplicationFactory SUT by @thomhurst in thomhurst/TUnit#5607 ### Dependencies * chore(deps): update tunit to 1.35.2 by @thomhurst in thomhurst/TUnit#5581 * chore(deps): update dependency typescript to ~6.0.3 by @thomhurst in thomhurst/TUnit#5582 * chore(deps): update dependency coverlet.collector to v10 by @thomhurst in thomhurst/TUnit#5600 **Full Changelog**: thomhurst/TUnit@v1.35.2...v1.36.0 Commits viewable in [compare view](thomhurst/TUnit@v1.35.2...v1.37.10). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
Part C of #5613. Adds a Roslyn code-fix for TUnit0049 (
MatrixDataSourceAttributeRequired) so the error is auto-repairable.When a method has
[Matrix]parameters (or a class has[Matrix]constructor parameters) but is missing[MatrixDataSource], TUnit0049 fires as an error. Previously the user had to hand-edit. Now the IDE offers Add [MatrixDataSource] which inserts the attribute on the diagnosed method or type.Note on scope
An earlier exploration tried making
[MatrixDataSource]implicit whenever[Matrix]params were present, but that touched source-gen + reflection engine + analyzer deletion and risked changing established semantics. A code-fix is a smaller, more discoverable change that leaves the explicit-attribute model intact.Test plan
Adds_MatrixDataSource_On_Method— method gains[MatrixDataSource]Adds_MatrixDataSource_On_Class— class gains[MatrixDataSource]